package com.ndbg.cmbpay.core;

import com.alibaba.fastjson.JSON;

import org.apache.commons.lang3.RandomStringUtils;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * 本程序旨在给商户联调提供测试demo，不对最终内容做质量保障，仅作为使用方参考。
 */
public class EncryptDemoV1 {

    private static X9ECParameters x9ECParameters = GMNamedCurves
            .getByName("sm2p256v1");
    private static ECDomainParameters ecDomainParameters = new ECDomainParameters(
            x9ECParameters.getCurve(), x9ECParameters.getG(),
            x9ECParameters.getN());
    private static ECParameterSpec ecParameterSpec = new ECParameterSpec(
            x9ECParameters.getCurve(), x9ECParameters.getG(),
            x9ECParameters.getN());

    static {
        if (Security.getProvider("BC") == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * 招行一网通API输出国密数字信封加密demo
     *
     * @throws Exception
     */
    public static void CmbEncDecyTest() throws Exception {
        // 配置银行公钥
        // 测试环境：X：e90f9f92db2763d3853fe2e9491e5475bc5fe731c214ed0f98e2a514d4f10c81
        // Y:a5f23b0f6db07ff444f6dcd57e69c4b3e05124cc3ef8b16da288d54744b88a1e
        // 测试环境对应银行公钥：04e90f9f92db2763d3853fe2e9491e5475bc5fe731c214ed0f98e2a514d4f10c81a5f23b0f6db07ff444f6dcd57e69c4b3e05124cc3ef8b16da288d54744b88a1e
        // 生产环境：X:23626E053B0ACFD6C026E740C5A18402C8686054CE006B365D0CC683DDCD956E
        // Y:BB7A6F4A49A712774CF42951EC57CEC9B4EDFC66B104C47C3BFAA482077426B3
        // 生产环境对应银行公钥：0423626E053B0ACFD6C026E740C5A18402C8686054CE006B365D0CC683DDCD956EBB7A6F4A49A712774CF42951EC57CEC9B4EDFC66B104C47C3BFAA482077426B3
        PublicKey publicKey = getPublickeyFromXY(
                new BigInteger(
                        "e90f9f92db2763d3853fe2e9491e5475bc5fe731c214ed0f98e2a514d4f10c81",
                        16),
                new BigInteger(
                        "a5f23b0f6db07ff444f6dcd57e69c4b3e05124cc3ef8b16da288d54744b88a1e",
                        16));

        /*----------------------------------------------*/
        //数字信封加密(SM4+SM2)
        //请在wOriTxt输入对应的明文
        //单字段加密以wOriTxt = {"A"}格式配置 其中A为json格式
        //多字段加密以wOriTxt = {"A","B","C"} A B C 为具体字段内容（不包括字段名称），加密后传输需进行替换原本字段值。
        String [] wOriTxt = {"{\n"
                + "    \"payCode\":\"37010324000000176600\"\n"
                + "}"};

        //随机生成一个SM4密钥(16位)
        String wSm4Key = RandomStringUtils.randomAlphanumeric(16);

        Map map = new HashMap<>();
        //SM4加密明文
        if(wOriTxt.length>1){
            map.put("zCipCnt",wOriTxt.length);
            for(String s : wOriTxt){
                byte[] wEncByt = sm4EncryptCBC(wSm4Key.getBytes(), s.getBytes("gbk"), SM4_CBC_IV);
                String data = new String(Base64.encode(wEncByt));
                for (int i = 0; i < wOriTxt.length; i++) {
                    map.put("xCipTxt"+i,data);
                }
            }
        }
        else{
            byte[] wEncByt = sm4EncryptCBC(wSm4Key.getBytes(), wOriTxt[0].getBytes("gbk"), SM4_CBC_IV);
            String data = new String(Base64.encode(wEncByt));
            map.put("xCipTxt",data);
            map.put("zCipLen",data.length());
        }

        //SM2加密对称密钥

        byte[] wEncKeyBytOldAsn1 = cipherRawToAsn1(sm2EncryptOld(wSm4Key.getBytes(), publicKey));
        String wDigEvp3 = new String(Base64.encode(wEncKeyBytOldAsn1));
        map.put("zDigEvp",wDigEvp3);
        map.put("zEvpLen",wDigEvp3.length());
        System.out.println(JSON.toJSONString(map));

    }

    /**
     * bc加解密使用旧标c1||c2||c3，此方法在加密后调用，将结果转化为c1||c3||c2
     *
     * @param c1c2c3
     * @return
     */
    private static byte[] changeC1C2C3ToC1C3C2(byte[] c1c2c3) {
        final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1; // sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
        final int c3Len = 32; // new SM3Digest().getDigestSize();
        byte[] result = new byte[c1c2c3.length];
        System.arraycopy(c1c2c3, 0, result, 0, c1Len); // c1
        System.arraycopy(c1c2c3, c1c2c3.length - c3Len, result, c1Len, c3Len); // c3
        System.arraycopy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.length
                - c1Len - c3Len); // c2
        return result;
    }

    /**
     * bc加解密使用旧标c1||c3||c2，此方法在解密前调用，将密文转化为c1||c2||c3再去解密
     *
     * @param c1c3c2
     * @return
     */
    private static byte[] changeC1C3C2ToC1C2C3(byte[] c1c3c2) {
        final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1; // sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
        final int c3Len = 32; // new SM3Digest().getDigestSize();
        byte[] result = new byte[c1c3c2.length];
        System.arraycopy(c1c3c2, 0, result, 0, c1Len); // c1: 0->65
        System.arraycopy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.length
                - c1Len - c3Len); // c2
        System.arraycopy(c1c3c2, c1Len, result, c1c3c2.length - c3Len, c3Len); // c3
        return result;
    }

    /**
     * c1||c2||c3
     *
     * @param data
     * @param key
     * @return
     */
    public static byte[] sm2EncryptOld(byte[] data, PublicKey key) {
        BCECPublicKey localECPublicKey = (BCECPublicKey) key;
        ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(
                localECPublicKey.getQ(), ecDomainParameters);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters,
                new SecureRandom()));
        try {
            return sm2Engine.processBlock(data, 0, data.length);
        } catch (InvalidCipherTextException e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] sm4Encrypt(byte[] keyBytes, byte[] plain) {
        if (keyBytes.length != 16) {
            throw new RuntimeException("err key length");
        }
        if (plain.length % 16 != 0) {
            throw new RuntimeException("err data length");
        }

        try {
            Key key = new SecretKeySpec(keyBytes, "SM4");
            Cipher out = Cipher.getInstance("SM4/ECB/NoPadding", "BC");
            out.init(Cipher.ENCRYPT_MODE, key);
            return out.doFinal(plain);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] sm4EncryptCBC(byte[] keyBytes, byte[] plain, byte[] iv) {
        if (keyBytes.length != 16) {
            throw new RuntimeException("err key length");
        }

        try {
            Key key = new SecretKeySpec(keyBytes, "SM4");
            Cipher out = Cipher.getInstance("SM4/CBC/PKCS5Padding", "BC");
            IvParameterSpec ivSpec = new IvParameterSpec(iv);
            out.init(Cipher.ENCRYPT_MODE, key, ivSpec);
            return out.doFinal(plain);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * @param bytes
     * @return
     */
    public static byte[] sm3(byte[] bytes) {
        SM3Digest sm3 = new SM3Digest();
        sm3.update(bytes, 0, bytes.length);
        byte[] result = new byte[sm3.getDigestSize()];
        sm3.doFinal(result, 0);
        return result;
    }

    private final static int RS_LEN = 32;

    private static byte[] bigIntToFixexLengthBytes(BigInteger rOrS) {
        // for sm2p256v1, n is
        // 00fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123,
        // r and s are the result of mod n, so they should be less than n and
        // have length<=32
        byte[] rs = rOrS.toByteArray();
        if (rs.length == RS_LEN) {
            return rs;
        } else if (rs.length == RS_LEN + 1 && rs[0] == 0) {
            return Arrays.copyOfRange(rs, 1, RS_LEN + 1);
        } else if (rs.length < RS_LEN) {
            byte[] result = new byte[RS_LEN];
            Arrays.fill(result, (byte) 0);
            System.arraycopy(rs, 0, result, RS_LEN - rs.length, rs.length);
            return result;
        } else {
            throw new RuntimeException("err rs: " + Hex.toHexString(rs));
        }
    }


    public static KeyPair generateKeyPair() {
        try {
            KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EC", "BC");
            kpGen.initialize(ecParameterSpec, new SecureRandom());
            KeyPair kp = kpGen.generateKeyPair();
            return kp;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static BCECPublicKey getPublickeyFromXY(BigInteger x, BigInteger y) {
        ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters
                .getCurve().createPoint(x, y), ecParameterSpec);
        return new BCECPublicKey("EC", ecPublicKeySpec,
                BouncyCastleProvider.CONFIGURATION);
    }


    /* 以下为提供的测试demo */

    public final static String USER_ID = "1234567812345678";
    public final static byte[] SM4_CBC_IV = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    /**
     * 招行一网通API输出国密密钥SM2生成demo
     */
    public static void CmbGenKeyTest() {

        // 生成公私钥对 ---------------------
        KeyPair kp = generateKeyPair();
        BCECPrivateKey prikey = (BCECPrivateKey) kp.getPrivate();
        BCECPublicKey pubkey = (BCECPublicKey) kp.getPublic();

        //System.out.println("私钥(10进制,不推荐存储格式):" + prikey.getD().toString(10));
        System.out.println("私钥(HEX,推荐存储格式):" + prikey.getD().toString(16));
        System.out.println("公钥X:" + pubkey.getQ().getAffineXCoord() + ": " + pubkey.getQ().getAffineXCoord().toString().length());
        System.out.println("公钥Y:" + pubkey.getQ().getAffineYCoord() + ": " + pubkey.getQ().getAffineYCoord().toString().length());
        if (pubkey.getQ().getAffineXCoord().toString().length() != 64) {
            System.out.println("-----------------");
        }

    }

    public static void main(String[] args) throws Exception {
        long start2 = System.currentTimeMillis();
        CmbEncDecyTest();
        System.out.println("CmbEncDecyTest consumes " + (System.currentTimeMillis() - start2) + "ms");

    }


    /**
     * 将BC C1C2C3 SM2 密文转化为ASN1格式C1C3C2密文
     *
     * @return byte[] ASN1格式密文
     */
    public static byte[] cipherRawToAsn1(byte[] bcCipTxt) throws Exception {

        //去除RAW密文开头的 04 标志位
        int bcCipLen = bcCipTxt.length - 1;
        if (96 >= bcCipLen) {
            throw new Exception("RAW ciphertext length error:" + bcCipLen);
        }

        //计算密文部分实际长度
        int C3Len = bcCipLen - 96;

        byte[] keyX = new byte[32];
        byte[] keyY = new byte[32];
        byte[] C3 = new byte[C3Len];
        byte[] C2 = new byte[32];

        System.arraycopy(bcCipTxt, 1, keyX, 0, 32);
        System.arraycopy(bcCipTxt, 33, keyY, 0, 32);
        System.arraycopy(bcCipTxt, 65, C3, 0, C3Len);
        System.arraycopy(bcCipTxt, 65 + C3Len, C2, 0, 32);

        byte[] netSignCipTxt = new byte[bcCipLen + 13];

        //keyX补位
        int wPos = 4;
        netSignCipTxt[0] = 0x30;
        netSignCipTxt[2] = 0x02;
        if ((keyX[0] & 0xFF) >= 128) {
            netSignCipTxt[wPos - 1] = 0x21;
            netSignCipTxt[wPos] = 0x00;
            wPos += 1;
        } else {
            netSignCipTxt[wPos - 1] = 0x20;
        }
        System.arraycopy(keyX, 0, netSignCipTxt, wPos, 32);
        wPos += 32;

        //keyY补位
        netSignCipTxt[wPos] = 0x02;
        wPos += 1;
        if ((keyY[0] & 0xFF) >= 128) {
            netSignCipTxt[wPos] = 0x21;
            wPos += 1;
            netSignCipTxt[wPos] = 0x00;
            wPos += 1;
        } else {
            netSignCipTxt[wPos] = 0x20;
            wPos += 1;
        }
        System.arraycopy(keyY, 0, netSignCipTxt, wPos, 32);
        wPos += 32;

        //copy C2
        netSignCipTxt[wPos] = 0x04;
        wPos += 1;
        netSignCipTxt[wPos] = 0x20;
        wPos += 1;
        System.arraycopy(C2, 0, netSignCipTxt, wPos, 32);
        wPos += 32;

        //copy C3
        netSignCipTxt[wPos] = 0x04;
        wPos += 1;
        netSignCipTxt[wPos] = (byte) C3Len;
        wPos += 1;
        System.arraycopy(C3, 0, netSignCipTxt, wPos, C3Len);
        wPos += C3Len;

        //总长度
        netSignCipTxt[1] = (byte) (wPos - 2);

        byte[] resultBytes = new byte[wPos];
        System.arraycopy(netSignCipTxt, 0, resultBytes, 0, wPos);

        return resultBytes;
    }



}